Vision Matrix Project Report

May 16th 2024
Kunal Kumar kk858 | Preston Whaley pw463 | Karim Pareja kgp33

Objective

The objective of this project is to develop a smart art installation that allows users to interact with and draw on the LED matrix using gesture-based inputs. The interface will harness pose information to enable intuitive and engaging art creations.

Project Video

Introduction

In this project, we developed Vision Matrix, an interactive art installation designed to engage users through dynamic visual representations. We utilized a combination of two Raspberry Pis, a PiTFT display, an LED matrix, and a camera to bring this concept to life. The installation captures the intersection of technology and art, transforming real-time video data into interactive displays on an LED matrix.

The Vision Matrix operates through a dual-system setup. The primary Raspberry Pi processes video inputs to create real-time art manifestations such as silhouettes and gesture drawings, which are then displayed on the LED matrix. The secondary Raspberry Pi serves as an interactive control panel via the PiTFT display, allowing users to select between three modes:

Design and Testing

In this section, we are going to discuss the design, testing, and any issues that we ran into. This includes the front-end, display & back-end processing, back-end communication protocol & code processing, and the mechanical components.

Front-End

Design

The Vision Matrix’s front-end is designed using Pygame to offer an interactive interface that allows users to navigate between three distinct modes. Each mode is linked to specific functionalities and socket communications with a server for further processing.

Testing

The front-end development testing focused primarily on ensuring usability on the piTFT's compact display. This involved adjusting the button sizes to ensure they were large enough to facilitate easy interaction for users. Additional testing was performed on the save functionality to confirm that names were correctly written to and retrieved from the JSON file. We also rigorously tested the gallery display to ensure that artworks were correctly ordered and displayed, enhancing the user experience by ensuring reliability and ease of access to saved drawings.

Issues

In developing the front-end display, we faced several challenges, notably with getting Pygame to render correctly on the piTFT screen. The primary issue stemmed from our initial use of HEX color values for font colors, which the piTFT could not process effectively, requiring a switch to RGB color values.

Art Mode
Art Mode Example
Mirror Mode
Mirror Mode Example
Gallery Mode
Gallery Mode Example

Display & Back End Processing

Design

RBG LED Matrix

The RGB LED matrix design involved wiring two 32x64 RGB matrices in a parallel chain to a Raspberry Pi, creating a 64x64 square panel for a better viewing experience. Parallel wiring was chosen over daisy-chaining to achieve a higher refresh rate and ease of displaying images. Although daisy-chaining would require splitting the image, vertical splitter mode in the rpi-rgb-led-matrix library by hzeller could facilitate this. However, parallel wiring offered a simpler solution with up to three parallel chains.

Display Pinout MediaPipe

For the image processing, we used mediapipe library to have four modes- Mirror mode, Stick mode, Disco mode, and the Art mode. The Mirror Mode used the selfie segmentation model from the Mediapipe library, performing well for solo individuals with minimal latency but struggling with multiple people or close-up shots. Disco Mode employed the same mask as Mirror Mode, looping through rainbow colors for each frame. Stick Mode used the pose landmarks model to track 33 connections, showing poses on the screen with some latency. Art Mode leveraged the hand landmarks model to track the index tip for drawing, improving drawing smoothness by increasing the brush size, drawing every third frame, and reducing the resolution to 320x240 for faster processing. Additional features included an eraser, image saving, and color changes via the piTFT attached to the wrist.

Issues

The RGB LED matrix faced a flickering issue with one panel after wiring, while the other worked fine. Swapping the problematic panel with a known working one didn’t resolve the flickering. Various parameters from the library, such as led_pwm_bits and led_limit_refresh, were tested without success. The issue was finally resolved by setting led_gpio_slowdown to 5, even though the documentation recommended a maximum of 4. Installing Mediapipe on the Raspberry Pi also posed challenges due to a lack of clear OS version requirements. The initial attempt on Bullseye 32-bit OS required Bazel, which was unavailable for 32-bit, necessitating an upgrade to 64-bit. However, the installation only worked on Ubuntu, making the upgrade futile. Following a professor’s suggestion, a guide from previous projects, Gesture Home and Air Painter, was used, which worked on Buster 32-bit OS. Although this older library had slower models, it was functional.

Testing

Testing the RGB LED matrix involved resolving the flickering issue by adjusting the led_gpio_slowdown parameter, after which the matrices displayed images without issues. Future improvements could include using a PCB adapter (design provided in the hzeller github repository) to simplify the wiring process. Testing Mediapipe models included verifying Mirror Mode for latency and performance with single and multiple individuals, checking Disco Mode for the color change effect in each frame, and assessing Stick Mode for pose tracking accuracy and response time. Art Mode was tested for drawing smoothness, brush size, frame rate adjustments, erasing, image saving, and color changes with the piTFT. The process highlighted the importance of compatible hardware, software, and thorough testing to address unexpected issues.

Art/Drawing Mode
Drawing Mode Example
Mirror (stickman/pose) Mode
Mirror (stickman) Mode Example
Gallery Mode
Gallery Mode Example

Back End Communication & Code Execution

Design

We approached communication between the Pi 0 and the RPi 4 using Python sockets. We utilized two ports, 5000 and 5001, to send messages from the Pi 0 to the RPi 4.

Port 5000 was dedicated to executing code scripts. These messages were used to run Mirror Mode, Draw Mode, or Gallery Mode. Once the Pi 0 sent the command, the receiver script, in the RPi 4, parsed it and executed the appropriate script file using the subprocess library and sudo -E command. Additionally, the receiver script tracked the running process and terminated it when switching modes.

Port 5001 handled all information related to selecting colors or naming the canvas when saving. This port was created and listened to by the drawing script, ensuring that all messages were directed to it. This also created a separation between the port that should only be listened to by the execution file, and the one that should only be listened to by the drawing file.

Issues

The main issue we faced was terminating the running script when executing a new script command in order to switch modes. Initially, we tried to terminate the last running Python3 process by parsing all the running processes. However, this caused problems when we implemented the drawing functionality, as each new socket created a new Python process.

In order to solve this, we created a function in the main receiver script that tracked the last executed process using a variable. This function then terminated the previous process, by getting the PID from that variable, before switching to a new mode.

Testing

Testing was conducted in two stages: using an external laptop and the PiTFT.

First, we tested with an external laptop by connecting to the appropriate port to ensure the commands were being correctly parsed and executed. This approach saved us time, as we didn't need to repeatedly boot up the PiTFT and search for error messages.

Once we confirmed that everything worked correctly on the laptop, we moved on to testing with the PiTFT. For this stage, we clicked the screen buttons to verify that the code executed correctly.

Mechanical Components

Design

For our project, we aimed to ensure that the user had access to their hands, as our project heavily relies on hand gestures and requires interaction with the PiTFT screen. To achieve this, we designed a wristwatch compartment for the PiTFT and Raspberry Pi Zero, allowing the PiTFT interface to be worn on the user’s wrist.The Raspberry Pi Zero is connected to an external power supply that can be conveniently carried in the user’s pocket. The wristwatch compartment is secured to the user’s wrist using a Velcro strap.

To provide a larger interactive canvas for the user, we connected two LED matrices. We achieved this by 3D printing small adapters that attached to the back of each LED display, enabling us to chain them together. Additionally, we designed a mount to conveniently position our camera at the top of the LED matrix.

Testing

We aimed to ensure that the wrist strap securely fit wrists of various sizes and that the Velcro strap could withstand the weight of the PiTFT and Raspberry Pi Zero. To verify this, we tested the longevity and comfort of wearing the wristwatch in different orientations.

Issues

The main problem was obtaining accurate measurements of the holes on the display, which lead to having to reprint the mounts.

PiTFT watch
Image 1
Mounting Brackets
Image 2

Results

Overall, we were able to successfully implement and demonstrate all the planned features of our VisionMatrix project. The Mirror Mode, Stick Mode, and the additional Disco Mode operated smoothly, providing an engaging user experience. The Art Mode, after optimization of Mediapipe model parameters and adjustments to server communication to ensure seamless color switching, functioned exceptionally well.

During our testing phase, we observed that the performance of the different modes was consistent and responsive. The LED matrices displayed the visual effects and user drawings without noticeable lag. Analyzing the system performance using htop revealed a balanced usage of all four cores of the Raspberry Pi, indicating efficient handling of machine learning and image processing tasks.

In Mirror Mode, the real-time segmentation and background replacement were effective, with minimal latency, allowing for an immersive experience. Stick Mode accurately tracked and displayed user poses, while Disco Mode added a dynamic and colorful element to the user’s silhouette. Art Mode’s drawing and color selection features were user-friendly and intuitive, and the gallery retrieval system worked as intended, making saved artworks easily accessible.

The design of the wristwatch compartment for the piTFT and Raspberry Pi Zero proved to be practical and comfortable, allowing users to interact with the interface easily. The secure fit of the wrist strap, as well as the stability of the attached components, ensured a reliable user experience. Additionally, the parallel wiring of the RGB LED matrices provided a high refresh rate and a seamless visual display.

Conclusion

In conclusion, we successfully created an interactive art installation that allows users to draw on the LED matrix using gesture-based inputs. In Art Mode, users can select their drawing color and create artwork on the display, with the option to save their creations. In Mirror Mode, users can mirror their movements using up to three different effects: mirror/hologram, disco, or stick man. Finally, saved images can be retrieved and displayed on the RGB matrix in Gallery Mode. Overall, this was a successful and enjoyable project!

Future Work

Some future work that would tie back well to this project are the following:

Parts

References

Code Appendix


        import socket
        import subprocess
        import signal
        import os
        
        local_ip = ""
        port = 5000
        
        receiver_socket = socket.socket()
        receiver_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        receiver_socket.bind((local_ip, port))
        
        receiver_socket.listen()
        sender_socket, sender_address = receiver_socket.accept()
        print("Connection established with:", sender_address)
        
        current_process = None
        
        def kill_current_process():
            global current_process
            if current_process is not None:
                os.killpg(os.getpgid(current_process.pid), signal.SIGTERM)
                current_process.wait()
                current_process = None
        
        try:
            while True:
                received_data = sender_socket.recv(1024)
                if not received_data:
                    break
                received_message = received_data.decode().strip()
                
                print(f"Received message: {received_message}")
                
                # Kill current process if there is one
                kill_current_process()
        
                # Start new process based on the received message
                if received_message.startswith('art'):
                    current_process = subprocess.Popen("sudo -E python3 drawWithGesture.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
                elif received_message == "seg":
                    current_process = subprocess.Popen("sudo -E python3 imageSegmentation.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
                elif received_message == "pose":
                    current_process = subprocess.Popen("sudo -E python3 pose2Display.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
                elif received_message == "disco":
                    current_process = subprocess.Popen("sudo -E python3 disco.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
                elif received_message.startswith("gal_"):
                    image_name = received_message.split('_')[1]
                    current_process = subprocess.Popen(f"sudo -E python3 saved.py {image_name}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
                elif received_message == "kill":
                    subprocess.Popen("sudo reboot", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    break
        
                if current_process:
                    print(f"Started process with PID: {current_process.pid}")
        
        except Exception as e:
            print(f"An error occurred: {e}")
        finally:
            kill_current_process()
            print("Connection closed. Exiting.")
            receiver_socket.close()
            sender_socket.close()
                    

        import cv2
        import mediapipe as mp
        from rgbmatrix import RGBMatrix, RGBMatrixOptions
        from PIL import Image, ImageDraw
        from gesture_recognizer import is_fist
        import socket
        import time
        
        print("running")
        
        # Setup socket for receiving color change messages
        local_ip = ""
        port = 5001
        
        receiver_socket = socket.socket()
        receiver_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        receiver_socket.bind((local_ip, port))
        
        receiver_socket.listen()
        sender_socket, sender_address = receiver_socket.accept()
        print("Connection established with: ", sender_address)
        
        # Set up video capture
        video_object = cv2.VideoCapture(0)
        if not video_object.isOpened():
            print("Error opening video stream or file")
        
        # Initialize MediaPipe hands
        mp_hands = mp.solutions.hands
        
        # LED Matrix setup
        options = RGBMatrixOptions()
        options.rows = 32
        options.cols = 64
        options.chain_length = 1
        options.parallel = 2
        options.hardware_mapping = 'regular'
        options.gpio_slowdown = 5
        matrix = RGBMatrix(options=options)
        
        # Initialize an image for persistent drawing
        persistent_image = Image.new("RGB", (64, 64))
        draw_persistent = ImageDraw.Draw(persistent_image)
        
        # Define brush size (radius)
        brush_size = 1
        
        # FrameDrop
        frame_count = 0
        process_frame_interval = 3  # Process every 3rd frame
        
        # State variables
        drawing = True  # Attempt to pause the drawing
        
        # Non-blocking socket read
        sender_socket.setblocking(0)
        received_message = "black"
        
        # Use MediaPipe Hands within a 'with' block
        with mp_hands.Hands(min_detection_confidence=0.5, min_tracking_confidence=0.5, max_num_hands=1) as hands:
            while video_object.isOpened():
                try:
                    received_data = sender_socket.recv(1024)
                    received_message = received_data.decode().strip()
                    print(received_message)
                except BlockingIOError:
                    pass
        
                ret, frame = video_object.read()
                if not ret:
                    print("Ignoring empty camera frame.")
                    continue
        
                frame_count += 1
                if frame_count % process_frame_interval != 0:
                    continue
        
                # Flip the image horizontally for a laterally correct display, and convert the image to RGB
                frame = cv2.resize(cv2.flip(frame, 1), (320, 240))
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                results = hands.process(frame_rgb)
        
                # Copy the persistent image to the matrix image for displaying
                matrix_image = persistent_image.copy()
                draw = ImageDraw.Draw(matrix_image)
        
                possible_colors = ["red", "orange", "yellow", "green", "blue", "violet", "black", "white"]
        
                if received_message.startswith("at_name_"):
                    matrix_image.save(f"{received_message[8:]}.png")
                    time.sleep(2)
                    received_message = "black"
        
                if results.multi_hand_landmarks:
                    for hand_landmarks in results.multi_hand_landmarks:
                        # Check for gestures
                        if is_fist(hand_landmarks):
                            drawing = False
                        else:
                            drawing = True
        
                        if drawing:
                            # Highlight the index finger tip
                            index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
                            x_tip = int(index_tip.x * 64)
                            y_tip = int(index_tip.y * 64)
        
                            # Ensure received_message is a valid color
                            color = received_message if received_message in possible_colors else "black"
        
                            # Draw on the persistent image with specified color
                            draw_persistent.ellipse([(x_tip - brush_size, y_tip - brush_size), (x_tip + brush_size, y_tip + brush_size)], fill=color)
        
                            # Set crosshair color to white if drawing color is blue
                            crosshair_color = "white" if color == "blue" else "blue"
                            crosshair_size = 2
                            draw.line((x_tip - crosshair_size, y_tip, x_tip + crosshair_size, y_tip), fill=crosshair_color)
                            draw.line((x_tip, y_tip - crosshair_size, x_tip, y_tip + crosshair_size), fill=crosshair_color)
        
                # Update the matrix display with the updated matrix image
                matrix.SetImage(matrix_image.convert('RGB'))
        
                if cv2.waitKey(5) & 0xFF == 27:
                    break
        
        video_object.release()
        cv2.destroyAllWindows()
        matrix.Clear()
                    

        import cv2
        import mediapipe as mp
        from rgbmatrix import RGBMatrix, RGBMatrixOptions
        from PIL import Image, ImageDraw
        
        # Initialize MediaPipe Selfie Segmentation
        mp_selfie_segmentation = mp.solutions.selfie_segmentation
        segmentation = mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
        
        # Set up the video capture source
        video_object = cv2.VideoCapture(0)
        video_object.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
        video_object.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
        
        # LED Matrix setup
        options = RGBMatrixOptions()
        options.rows = 32
        options.cols = 64
        options.chain_length = 1
        options.parallel = 2
        options.hardware_mapping = 'regular'
        options.gpio_slowdown = 5
        matrix = RGBMatrix(options=options)
        
        while video_object.isOpened():
            ret, frame = video_object.read()
            if not ret:
                print("Ignoring empty camera frame.")
                continue
        
            # To improve performance, optionally mark the image as not writeable to
            # pass by reference.
            frame.flags.writeable = False
            # Process frame with MediaPipe Segmentation
            results = segmentation.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            segmentation_mask = results.segmentation_mask
        
            # Threshold the mask to create a binary image
            _, binary_mask = cv2.threshold(segmentation_mask, 0.5, 255, cv2.THRESH_BINARY)
        
            # Convert the binary mask to a PIL image
            mask_image = Image.fromarray(binary_mask).convert('L')
            mask_image = mask_image.resize((64, 64), Image.NEAREST)
        
            # Convert to RGB mode before displaying
            rgb_image = mask_image.convert('RGB')
        
            # Display the result on the LED matrix
            matrix.SetImage(rgb_image)
            
            # Exit logic
            if cv2.waitKey(5) & 0xFF == 27:
                break
        
        
        video_object.release()
        matrix.Clear()                        
                    

        import cv2
        import mediapipe as mp
        from rgbmatrix import RGBMatrix, RGBMatrixOptions
        from PIL import Image, ImageDraw
        
        # Initialize MediaPipe Pose and drawing utilities
        mp_pose = mp.solutions.pose
        mp_drawing = mp.solutions.drawing_utils
        
        # Set up the video capture source
        video_object = cv2.VideoCapture(0)
        video_object.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
        video_object.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
        
        # LED Matrix setup
        options = RGBMatrixOptions()
        options.rows = 32
        options.cols = 64
        options.chain_length = 1
        options.parallel = 2
        options.hardware_mapping = 'regular'
        options.gpio_slowdown = 5
        matrix = RGBMatrix(options=options)
        
        #FrameDrop
        frame_count = 0
        process_frame_interval = 3  # Process every 2nd frame
            
        with mp_pose.Pose(static_image_mode=False, 
                            min_detection_confidence=0.3, 
                            min_tracking_confidence=0.5) as pose:
            
            while video_object.isOpened():
                ret, frame = video_object.read()
                if not ret:
                    print("Ignoring empty camera frame.")
                    continue
                    
                frame_count += 1
                if frame_count % process_frame_interval != 0:
                    continue
        
                # To improve performance, optionally mark the image as not writeable to
                # pass by reference.
                frame.flags.writeable = False
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                results = pose.process(frame_rgb)
        
                # Create a blank image for the LED matrix
                matrix_image = Image.new("RGB", (64, 64))
                draw = ImageDraw.Draw(matrix_image)
        
                # Draw the pose annotations on the LED matrix image
                if results.pose_landmarks:
                    # Iterate through all connections
                    for connection in mp_pose.POSE_CONNECTIONS:
                        start_point = results.pose_landmarks.landmark[connection[0]]
                        end_point = results.pose_landmarks.landmark[connection[1]]
                        start_x, start_y = int(start_point.x * 64), int(start_point.y * 64)
                        end_x, end_y = int(end_point.x * 64), int(end_point.y * 64)
                        draw.line((start_x, start_y, end_x, end_y), fill="yellow", width=1)
        
                # Update the matrix display
                matrix.SetImage(matrix_image.convert('RGB'))
        
                # Exit logic
                if cv2.waitKey(5) & 0xFF == 27:
                    break
        
        video_object.release()
        cv2.destroyAllWindows()
        matrix.Clear()
                    

        import cv2
        import mediapipe as mp
        from rgbmatrix import RGBMatrix, RGBMatrixOptions
        from PIL import Image, ImageDraw, ImageOps
        
        # Initialize MediaPipe Selfie Segmentation
        mp_selfie_segmentation = mp.solutions.selfie_segmentation
        segmentation = mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
        
        # Set up the video capture source
        video_object = cv2.VideoCapture(0)
        video_object.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
        video_object.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
        
        # LED Matrix setup
        options = RGBMatrixOptions()
        options.rows = 32
        options.cols = 64
        options.chain_length = 1
        options.parallel = 2
        options.hardware_mapping = 'regular'
        options.gpio_slowdown = 5
        matrix = RGBMatrix(options=options)
        
        # Define Mario Super Star colors
        super_star_colors = [
            (255, 0, 0),    # Red
            (255, 255, 0),  # Yellow
            (0, 0, 255),    # Blue
            (0, 255, 0)     # Green
        ]
        color_index = 0
        
        
        while video_object.isOpened():
            ret, frame = video_object.read()
            if not ret:
                print("Ignoring empty camera frame.")
                continue
        
            # Resize and process frame with MediaPipe Segmentation
            results = segmentation.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            segmentation_mask = results.segmentation_mask
        
            # Threshold the mask to create a binary image
            _, binary_mask = cv2.threshold(segmentation_mask, 0.5, 255, cv2.THRESH_BINARY)
        
            # Convert the binary mask to a PIL image
            mask_image = Image.fromarray(binary_mask).convert('L')
            mask_image = mask_image.resize((64, 64), Image.NEAREST)
        
            # Apply current rainbow color to the mask
            colored_mask = ImageOps.colorize(mask_image, black="black", white=super_star_colors[color_index])
            color_index = (color_index + 1) % len(super_star_colors)  # Update color index
        
            # Display the result on the LED matrix
            matrix.SetImage(colored_mask.convert('RGB'))
            
            # Exit logic
            if cv2.waitKey(5) & 0xFF == 27:
                break
        
        
        video_object.release()
        matrix.Clear()
                    

        import cv2
        import mediapipe as mp
        from rgbmatrix import RGBMatrix, RGBMatrixOptions
        from PIL import Image, ImageDraw
        import sys
        import time
        #from receiver import received_message
        
        # LED Matrix setup
        options = RGBMatrixOptions()
        options.rows = 32
        options.cols = 64
        options.chain_length = 1
        options.parallel = 2
        options.hardware_mapping = 'regular'
        options.gpio_slowdown = 5
        matrix = RGBMatrix(options=options)
        
        image_path = str(sys.argv[1])+'.png'
        
        image = Image.open(image_path)
        
        #image = image.resize((64,64), Image.LANCZOS)
        
        matrix.SetImage(image.convert("RGB"))
        
        time.sleep(10)
        
        
        video_object.release()
        cv2.destroyAllWindows()
        matrix.Clear()
                    

        import pygame, pigame, sys
        from buttons import Button
        from pygame.locals import *
        import datetime
        import json
        import os
        import RPi.GPIO as GPIO
        from time import sleep
        import time
        import socket
        
        
        os.putenv('SDL_VIDEODRV', 'fbcon')
        os.putenv('SDL_FBDEV', '/dev/fb0')
        os.putenv('SDL_MOUSEDRV','dummy')
        os.putenv('SDL_MOUSEDEV','/dev/null')
        os.putenv('DISPLAY','')
        
        receiver_ip = '10.49.88.187'
        port = 5000
        
        port_color = 5001
        
        
        
        
        
        receiver_socket = socket.socket()
        receiver_socket.connect((receiver_ip, port))
        print("Connected to this IP: ", receiver_ip)
        
        
        pygame.init()
        pitft = pigame.PiTft()
        screen = pygame.display.set_mode((320, 240))
        BG = pygame.image.load("assets/background/background.png")
        white = (255,255,255)
        black= (0,0,0)
        clock=pygame.time.Clock()
        name_text = ""
        window = None
        save_data = {}
        TimeOut=60
        startTime = time.time()
        
        pygame.mouse.set_visible(False)
        
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        
        def button_17(channel):
            print("Quit")
            GPIO.cleanup()
            sys.exit()
        
        GPIO.add_event_detect(17, GPIO.FALLING, callback=button_17, bouncetime=300)
        
        try:    
            with open('save_data.txt') as save_file:
                save_data=json.load(save_file)
        except:
            print('No file created yet')
        
        
        def get_font(size):
            return pygame.font.Font("assets/fonts/bronovo.ttf", size)
        
        
        def preview(title):
            message = "gal_"+str(title)
            #message = "gal_art2"
            receiver_socket.send(message.encode())
            
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                SAVE_CONFIRM_MOUSE_POS = pygame.mouse.get_pos()
                BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Back", font=get_font(11), base_color=black)
                PREVIEW_TITLE = Button(image=None, pos=(160,122), text_input=title, font=get_font(22), base_color=black)
        
                #print(title)
                for button in [BACK_BUTTON, PREVIEW_TITLE]:
                    button.update(screen)   
        
                for event in pygame.event.get():
                    #if event.type == pygame.QUIT:
                        #   pygame.quit()
                        #  sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if BACK_BUTTON.checkForInput(SAVE_CONFIRM_MOUSE_POS):
                            gallery()
        
                pygame.display.update()
        
        def gallery():
            global save_data
            sorted_data = sorted(save_data.items(), key=lambda item: item[1], reverse=True)
            
            # Page setup
            index = 0
            items_per_page = 5
            max_index = len(sorted_data) - items_per_page if len(sorted_data) > items_per_page else 0
        
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                GALLERY_MOUSE_POS = pygame.mouse.get_pos()
        
                buttons = []
                title_buttons = []
                for i in range(items_per_page):
                    data_index = index + i
                    if data_index < len(sorted_data):
                        title, date = sorted_data[data_index]
                    else:
                        # If there are no more entries, display empty slots
                        title = "Empty"
                        date = "Empty"
        
                    date_button = Button(image=None, pos=(46, 85.5 + 28 * i), text_input=date, font=get_font(12), base_color=black)
                    title_button = Button(image=pygame.image.load("assets/text_boxes/gallery.png"), pos=(163, 84.5 + 28 * i), text_input=title, font=get_font(12), base_color=black)
                    buttons.extend([date_button, title_button])
                    title_buttons.append(title_button)
        
                # Navigation buttons
                BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Exit", font=get_font(11), base_color=black)
                UP_BUTTON = Button(image=pygame.image.load("assets/buttons/up.png"), pos=(279.5, 115.5))
                DOWN_BUTTON = Button(image=pygame.image.load("assets/buttons/down.png"), pos=(279.5,155.5))
                NEWEST_BUTTON = Button(image=pygame.image.load("assets/buttons/skip.png"), pos=(279, 61.5), text_input="Newest", font=get_font(14), base_color=white)
                OLDEST_BUTTON = Button(image=pygame.image.load("assets/buttons/skip.png"), pos=(279,216.5), text_input="Oldest", font=get_font(14), base_color=white)
                GALLERY = Button(image=None, pos=(156, 33), text_input= "Gallery", font=get_font(18), base_color=black)
                buttons.extend([BACK_BUTTON, UP_BUTTON, DOWN_BUTTON, NEWEST_BUTTON, OLDEST_BUTTON, GALLERY])
        
                for button in buttons:
                    button.update(screen)
        
                for event in pygame.event.get():
                    #if event.type == pygame.QUIT:
                        #   pygame.quit()
                        #  sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:
                        if BACK_BUTTON.checkForInput(GALLERY_MOUSE_POS):
                            print("Main_Menu")
                            main_menu()
                        elif UP_BUTTON.checkForInput(GALLERY_MOUSE_POS) and index > 0:
                            index -= 1
                        elif DOWN_BUTTON.checkForInput(GALLERY_MOUSE_POS) and index < max_index:
                            index += 1
                        elif NEWEST_BUTTON.checkForInput(GALLERY_MOUSE_POS):
                            index = 0
                        elif OLDEST_BUTTON.checkForInput(GALLERY_MOUSE_POS):
                            index = max_index
                        else:
                            for button in title_buttons:
                                if button.checkForInput(GALLERY_MOUSE_POS):
                                    preview(button.text_input)
                                    print("this is the gallery image")
                                    print(button.text_input)
        
                pygame.display.update()
        
        
        def name():
            global name_text, window
            window = "name"
            text_box_rect = pygame.Rect(50, 50, 220, 30)
        
            def handle_button_input(input_text):
                global name_text
                if input_text == "DEL":
                    name_text = name_text[:-1]  # Remove the last character
                elif input_text == "SPACE":
                    name_text += " "  # Add space
                elif input_text == "ENTER":
                    save(name_text)  # Use the text to show user name
                    name_text = ""  # Reset text after showing name
                else:
                    if len(name_text) < 18:
                        name_text += input_text 
                
            
        
            while True:
                pitft.update()
                time_delta = clock.tick(60)/1000.0
                screen.blit(BG, (0, 0))
                NAME_MOUSE_POS = pygame.mouse.get_pos()
                Q_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(34.5, 110.5), text_input="Q", font=get_font(17), base_color=white)
                W_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(61.5, 110.5), text_input="W", font=get_font(17), base_color=white)
                E_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(88.5, 110.5), text_input="E", font=get_font(17), base_color=white)
                R_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(115.5, 110.5), text_input="R", font=get_font(17), base_color=white)
                T_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(142.5, 110.5), text_input="T", font=get_font(17), base_color=white)
                Y_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(169.5, 110.5), text_input="Y", font=get_font(17), base_color=white)
                U_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(196.5, 110.5), text_input="U", font=get_font(17), base_color=white)
                I_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(223.5, 110.5), text_input="I", font=get_font(17), base_color=white)
                O_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(250.5, 110.5), text_input="O", font=get_font(17), base_color=white)
                P_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(277.5, 110.5), text_input="P", font=get_font(17), base_color=white)
                A_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(48.5, 144.5), text_input="A", font=get_font(17), base_color=white)
                S_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(75.5, 144.5), text_input="S", font=get_font(17), base_color=white)
                D_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(102.5, 144.5), text_input="D", font=get_font(17), base_color=white)
                F_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(129.5, 144.5), text_input="F", font=get_font(17), base_color=white)
                G_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(156.5, 144.5), text_input="G", font=get_font(17), base_color=white)
                H_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(183.5, 144.5), text_input="H", font=get_font(17), base_color=white)
                J_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(210.5, 144.5), text_input="J", font=get_font(17), base_color=white)
                K_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(237.5, 144.5), text_input="K", font=get_font(17), base_color=white)
                L_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(264.5, 144.5), text_input="L", font=get_font(17), base_color=white)
                Z_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(76.5, 179.5), text_input="Z", font=get_font(17), base_color=white)
                X_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(103.5, 179.5), text_input="X", font=get_font(17), base_color=white)
                C_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(130.5, 179.5), text_input="C", font=get_font(17), base_color=white)
                V_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(157.5, 179.5), text_input="V", font=get_font(17), base_color=white)
                B_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(184.5, 179.5), text_input="B", font=get_font(17), base_color=white)
                N_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(211.5, 179.5), text_input="N", font=get_font(17), base_color=white)
                M_BUTTON = Button(image=pygame.image.load("assets/text_boxes/key_text.png"), pos=(238.5, 179.5), text_input="M", font=get_font(17), base_color=white)
                SPACE_BUTTON = Button(image=pygame.image.load("assets/text_boxes/space_key.png"), pos=(154.5, 211.5), text_input="SPACE", font=get_font(19), base_color=white)
                ENTER_BUTTON = Button(image=pygame.image.load("assets/text_boxes/enter_key.png"), pos=(266.5, 211.5), text_input="ENTER", font=get_font(17), base_color=white)
                DEL_BUTTON = Button(image=pygame.image.load("assets/text_boxes/del_key.png"), pos=(279, 179.5), text_input="DEL", font=get_font(16), base_color=white)
                NAME = Button(image=None, pos=(159.5,24), text_input="Name", font=get_font(16), base_color=black)
                BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Back", font=get_font(11), base_color=black)
                
                
                button_list = [Q_BUTTON, W_BUTTON, E_BUTTON, R_BUTTON, T_BUTTON, Y_BUTTON, U_BUTTON, I_BUTTON, O_BUTTON, 
                                P_BUTTON, A_BUTTON, S_BUTTON, D_BUTTON, F_BUTTON, G_BUTTON, H_BUTTON, J_BUTTON, K_BUTTON, 
                                L_BUTTON, Z_BUTTON, X_BUTTON, C_BUTTON, V_BUTTON, B_BUTTON, N_BUTTON, M_BUTTON, SPACE_BUTTON,
                                DEL_BUTTON, ENTER_BUTTON]
                
                button_list2 = [NAME, BACK_BUTTON]
                
                for button in button_list:
                    button.update(screen)
                for button in button_list2:
                    button.update(screen)
        
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit()
                    for button in button_list:
                        if event.type == pygame.MOUSEBUTTONDOWN:    
                            if button.checkForInput(NAME_MOUSE_POS):
                                handle_button_input(button.text_input)
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if BACK_BUTTON.checkForInput(NAME_MOUSE_POS):
                            if len(name_text) == 0:
                                save(name_text)
                            else:
                                back_confirm()
                
                text_surface = get_font(24).render(name_text, True, pygame.Color('black'))
                screen.blit(text_surface, (text_box_rect.x + 5, text_box_rect.y + 5))  # Adjust text position inside the box
                pygame.draw.rect(screen, pygame.Color('white'), text_box_rect, 2) 
        
                pygame.display.update()
            
        def back_confirm():
            global window, name_text
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                SAVE_CONFIRM_MOUSE_POS = pygame.mouse.get_pos()
                YES_BUTTON = Button(image=pygame.image.load("assets/buttons/yes.png"), pos=(103,140), text_input="Yes", font=get_font(14), base_color=black)
                CANCEL_BUTTON = Button(image=pygame.image.load("assets/buttons/cancel.png"), pos=(215,140), text_input="Cancel", font=get_font(13), base_color=black)
                TEXT = Button(image=None, pos=(160,86), text_input="Are you sure that you dont want to save?", font=get_font(14), base_color=black)
        
                for button in [YES_BUTTON, CANCEL_BUTTON, TEXT]:
                    button.update(screen)   
        
                for event in pygame.event.get():
                    #  if event.type == pygame.QUIT:
                    #     pygame.quit()
                    #    sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if YES_BUTTON.checkForInput(SAVE_CONFIRM_MOUSE_POS):
                            if window == "artmode":
                                #color_socket.close()
                                main_menu()
                            elif window == "save":
                                name_text=""
                                art_mode()
                            elif window == "name":
                                name_text=""
                                save(name_text)
                        if CANCEL_BUTTON.checkForInput(SAVE_CONFIRM_MOUSE_POS):
                            if window == "artmode":
                                art_mode()
                            if window == "save":
                                save(name_text)
                            if window == "name":
                                name()
        
                pygame.display.update()
        
        def save_confirm():
            global window, name_text
            current_date = datetime.datetime.now().date().strftime("%Y-%m-%d")
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                SAVE_CONFIRM_MOUSE_POS = pygame.mouse.get_pos()
                YES_BUTTON = Button(image=pygame.image.load("assets/buttons/yes.png"), pos=(113,179), text_input="Yes", font=get_font(14), base_color=black)
                CANCEL_BUTTON = Button(image=pygame.image.load("assets/buttons/cancel.png"), pos=(215,179), text_input="Cancel", font=get_font(13), base_color=black)
                TEXT = Button(image=None, pos=(160,59), text_input="Confirm Save?", font=get_font(14), base_color=black)
                NAME = Button(image=None, pos=(160.5,99), text_input=name_text, font=get_font(14), base_color=black)
                DATE = Button(image=None, pos=(160.5,119), text_input=current_date, font=get_font(14), base_color=black)
        
                for button in [YES_BUTTON, CANCEL_BUTTON, TEXT, NAME, DATE]:
                    button.update(screen)   
        
                for event in pygame.event.get():
                    # if event.type == pygame.QUIT:
                    #    pygame.quit()
                        #   sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if YES_BUTTON.checkForInput(SAVE_CONFIRM_MOUSE_POS):
                            save_data[name_text]=current_date
                            with open('save_data.txt','w') as save_file:
                                json.dump(save_data,save_file)
                            print(name_text)
                            message = "at_name_"+name_text
                            color_socket.send(message.encode())
                            
                            name_text=""
                            #print(name_text)
                            gallery()
                        if CANCEL_BUTTON.checkForInput(SAVE_CONFIRM_MOUSE_POS):
                            save(name_text)
        
                pygame.display.update()
        
        def save(name_text):
            global window
            window = "save"
            display_error = False
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                SAVE_MOUSE_POS = pygame.mouse.get_pos()
        
                if display_error:
                    title_input = "Name is Required"
                    title_color = (255,0,0)
                else:
                    title_input = name_text if name_text else ""
                    title_color = black
        
                TITLE_BUTTON = Button(image=pygame.image.load("assets/text_boxes/save.png"), pos=(192,126), text_input=title_input, font=get_font(14), base_color=title_color)
                DATE_BUTTON = Button(image=None, pos=(134,166), text_input=datetime.datetime.now().date().strftime("%Y-%m-%d"), font=get_font(14), base_color=black)
                NAME = Button(image=None, pos=(52,126), text_input="Name:", font=get_font(14), base_color=black)
                DATE = Button(image=None, pos=(55,166), text_input="Date:", font=get_font(15), base_color=black)
                TEXT = Button(image=None, pos=(166,63), text_input="Save", font=get_font(16), base_color=black)
                BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Back", font=get_font(11), base_color=black)
                SAVE_BUTTON = Button(image=pygame.image.load("assets/buttons/save.png"), pos=(260,185), text_input="Save", font=get_font(13), base_color=black)
        
                for button in [TITLE_BUTTON, DATE_BUTTON, NAME, DATE_BUTTON, DATE, TEXT, BACK_BUTTON, SAVE_BUTTON]:
                    button.update(screen)   
        
                for event in pygame.event.get():
                #    if event.type == pygame.QUIT:
                    #       pygame.quit()
                    #      sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if BACK_BUTTON.checkForInput(SAVE_MOUSE_POS):
                            if len(name_text) == 0:
                                art_mode()
                            back_confirm()
                        if TITLE_BUTTON.checkForInput(SAVE_MOUSE_POS):
                            name()
                        if SAVE_BUTTON.checkForInput(SAVE_MOUSE_POS):
                            if len(name_text) == 0:
                                display_error = True  
                            else:
                                display_error = False  
                                save_confirm()
        
                pygame.display.update()
        
        def art_mode():
            
            time.sleep(10)
            
            global color_socket
            color_socket = socket.socket()
            color_socket.connect((receiver_ip, port_color))
            print("Connected to color port: ", receiver_ip)
            
            #now = time.time()
            #elapsedTime = now - startTime
            #if elapsedTime > TimeOut:
            #    pygame.quit()
            #    sys.exit()
            global window
            window = "artmode"
        
            BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Back", font=get_font(11), base_color=black)
            SAVE_BUTTON = Button(image=pygame.image.load("assets/buttons/save.png"), pos=(260,185), text_input="Save", font=get_font(13), base_color=black)
            TITLE = Button(image=None, pos=(42,13), text_input="Art Mode", font=get_font(12), base_color=black)
        
            YELLOW = Button(image=pygame.image.load("assets/circles/yellow.png"), pos=(52,86))
            YELLOW_CLICKED = Button(image=pygame.image.load("assets/circles/yellow_clicked.png"), pos=(52,86))
            RED = Button(image=pygame.image.load("assets/circles/red.png"), pos=(145,33))
            RED_CLICKED = Button(image=pygame.image.load("assets/circles/red_clicked.png"), pos=(145,33))
            BLUE_CLICKED = Button(image=pygame.image.load("assets/circles/blue_clicked.png"), pos=(46,187))
            BLUE = Button(image=pygame.image.load("assets/circles/blue.png"), pos=(46,187))
            VIOLET_CLICKED = Button(image=pygame.image.load("assets/circles/violet_clicked.png"), pos=(96,210))
            VIOLET = Button(image=pygame.image.load("assets/circles/violet.png"), pos=(96,210))
            WHITE_CLICKED = Button(image=pygame.image.load("assets/circles/white_clicked.png"), pos=(152,197))
            WHITE = Button(image=pygame.image.load("assets/circles/white.png"), pos=(152,197))
            ORANGE_CLICKED = Button(image=pygame.image.load("assets/circles/orange_clicked.png"), pos=(94,53))
            ORANGE = Button(image=pygame.image.load("assets/circles/orange.png"), pos=(94,53))
            GREEN_CLICKED = Button(image=pygame.image.load("assets/circles/green_clicked.png"), pos=(34,136))
            GREEN = Button(image=pygame.image.load("assets/circles/green.png"), pos=(34,136))
            ERASE = Button(image=pygame.image.load("assets/buttons/erase.png"), pos=(256,98), text_input="Erase", font=get_font(15), base_color=black)
            ERASE_CLICKED = Button(image=pygame.image.load("assets/buttons/erase_clicked.png"), pos=(256,98), text_input="Erase", font=get_font(15), base_color=(213,212,217))
        
            button_on = ["ERASE"]
        
            colors = {"YELLOW" : {"state": False, "clicked": YELLOW_CLICKED, "unclicked": YELLOW}, 
                        "RED" : {"state": False, "clicked": RED_CLICKED, "unclicked": RED},
                        "BLUE" : {"state": False, "clicked": BLUE_CLICKED, "unclicked": BLUE},
                        "VIOLET" : {"state": False, "clicked": VIOLET_CLICKED, "unclicked": VIOLET},
                        "WHITE" : {"state": False, "clicked": WHITE_CLICKED, "unclicked": WHITE},
                        "ORANGE" : {"state": False, "clicked": ORANGE_CLICKED, "unclicked": ORANGE},
                        "GREEN" : {"state": False, "clicked": GREEN_CLICKED, "unclicked": GREEN},
                        "BLACK": {"state": False, "clicked": ERASE_CLICKED, "unclicked": ERASE}}
            
            def is_any_color_active():
                return any(value['state'] for key, value in colors.items() if key != "ERASE")
        
            def update_buttons(button_dict, screen):
                for key, value in button_dict.items():
                    if value['state']:
                        value['clicked'].update(screen)
                    else:
                        value['unclicked'].update(screen)
        
            def check_clicked(button_dict):
                nonlocal button_on
                for key, value in button_dict.items():
                    if value['unclicked'].checkForInput(ART_MOUSE_POS) or value['clicked'].checkForInput(ART_MOUSE_POS):
                        if button_on[0] != key:
                            if button_on[0] in button_dict:
                                button_dict[button_on[0]]['state'] = False
                            button_on[0] = key
                        value['state'] = not value['state']
                        message = str(key.lower())
                        print(message)
                        color_socket.send(message.encode())
        
            while True:
                pitft.update()
                screen.blit(BG, (0, 0))
                ART_MOUSE_POS = pygame.mouse.get_pos()
        
                update_buttons(colors, screen)
                
                for button in [BACK_BUTTON, SAVE_BUTTON, TITLE]:
                    button.update(screen)  
        
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit()
                    elif event.type == pygame.MOUSEBUTTONDOWN:
                        check_clicked(colors)
                        if BACK_BUTTON.checkForInput(ART_MOUSE_POS):
                            if is_any_color_active():
                                back_confirm()
                            color_socket.close()
                            main_menu()
                        if SAVE_BUTTON.checkForInput(ART_MOUSE_POS):
                            save(name_text)
        
                pygame.display.update()
        
        
        def mirror_mode():
            while True:
                pitft.update()
                screen.blit(BG, (0,0))
                MIRROR_MODE_MOUSE_POS = pygame.mouse.get_pos()
                BACKGROUND_1 = Button(image=pygame.image.load("assets/buttons/background.png"), pos=(161,66), text_input="Mirror", font=get_font(16), base_color=black)
                BACKGROUND_2 = Button(image=pygame.image.load("assets/buttons/background.png"), pos=(161,131), text_input="Stick", font=get_font(16), base_color=black)
                BACKGROUND_3 = Button (image=pygame.image.load("assets/buttons/background.png"), pos=(161,196), text_input="Disco", font=get_font(16), base_color=black)
                BACK_BUTTON = Button(image=pygame.image.load("assets/buttons/back.png"), pos=(282,20), text_input="Back", font=get_font(11), base_color=black)
                TITLE = Button(image=None, pos=(45,13), text_input="Mirror Mode", font=get_font(12), base_color=black)
            
        
                for button in [BACKGROUND_1, BACKGROUND_2, BACKGROUND_3, BACK_BUTTON, TITLE]:
                    button.update(screen)  
                    
        
                for event in pygame.event.get():
                    #   if event.type == pygame.QUIT:
                    #      pygame.quit()
                    #     sys.exit()
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if BACKGROUND_1.checkForInput(MIRROR_MODE_MOUSE_POS):
                            print("BACKGROUND 1")
                            message = "seg"
                            receiver_socket.send(message.encode())
                        if BACKGROUND_2.checkForInput(MIRROR_MODE_MOUSE_POS):
                            print("BACKGROUND 2")
                            message = "pose"
                            receiver_socket.send(message.encode())
                        if BACKGROUND_3.checkForInput(MIRROR_MODE_MOUSE_POS):
                            print("BACKGROUND 3")
                            message = "disco"
                            receiver_socket.send(message.encode())
                        if BACK_BUTTON.checkForInput(MIRROR_MODE_MOUSE_POS):
                            print("back")
                            main_menu()
                pygame.display.update()
        
        def main_menu(): # main menu screen
        
            message_sent = False  # Flag to track if the message has been sent
        
            while True:
                if not message_sent:
                    message = "menu"
                    receiver_socket.send(message.encode())
                    message_sent = True
        
                pitft.update()
                screen.blit(BG, (0,0))
                MENU_MOUSE_POS = pygame.mouse.get_pos()
                MIRROR_BUTTON = Button(image=pygame.image.load("assets/buttons/mirror.png"), pos=(88,146), text_input="Mirror Mode", font=get_font(15), base_color=black)
                ART_BUTTON = Button(image=pygame.image.load("assets/buttons/art.png"), pos=(228,146), text_input="Art Mode", font=get_font(15), base_color=black)
                GALLERY_BUTTON = Button (image=pygame.image.load("assets/buttons/gallery.png"), pos=(159.5,202), text_input="Enter the Gallery", font=get_font(15), base_color=black)
                TITLE = Button (image=pygame.image.load("assets/text/title.png"), pos=(162,63))
                QUIT = Button (image=pygame.image.load("assets/buttons/quit.png"), pos=(20,20))
        
        
                for button in [MIRROR_BUTTON, ART_BUTTON, GALLERY_BUTTON, TITLE, QUIT]:
                    button.update(screen) 
        
                for event in pygame.event.get():
                    if event.type == pygame.MOUSEBUTTONDOWN:    
                        if MIRROR_BUTTON.checkForInput(MENU_MOUSE_POS):
                            mirror_mode()
                        if ART_BUTTON.checkForInput(MENU_MOUSE_POS):
                            message = "art"
                            receiver_socket.send(message.encode())
                            art_mode()
                        if GALLERY_BUTTON.checkForInput(MENU_MOUSE_POS):
                            gallery()
                        if QUIT.checkForInput(MENU_MOUSE_POS): 
                            pygame.quit()
                            sys.exit()
                            
                pygame.display.update()
        
        main_menu()
                        
                    

Team

Team Photo 1 Team Photo 2

Preston Whaley pw463

    Team Member Preston
      Preston worked on the front-end design for the PiTFT display. He also developed the mounting mechanisms for the wrist, display, and camera, making sure they were both secure and aesthetically integrated into the overall design.

      Kunal Kumar kk858

        Team Member Kunal
          Kunal worked on the backend processing algorithms, designing and optimizing the code for image analysis and display on the RGB matrix. He also handled the wiring for the parallel RGB matrix display.

          Karim Pareja kgp33

            Team Member Karim
              Karim worked on the communication protocol between the Raspberry Pis and the code execution logic. He also developed the functionality for locally saving and displaying artworks.